﻿using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.ComponentModel;
using System.Windows.Forms.Design;
using System.Drawing.Design;
using FrameWork.Setting;

namespace ScreenMonitor.Common.File
{

    /// <summary>
    /// 表示单个文件在文件池中存储的信息
    /// </summary>
    public sealed class FileItemInfo
    {
        /// <summary>
        /// 获取该文件是否用来写入
        /// </summary>
        public bool IsWrite { get; private set; }
        /// <summary>
        /// 表示该文件的引用数量
        /// </summary>
        public int RefCount { get; set; }
        /// <summary>
        /// 表示该文件最后一次操作的服务器时间
        /// </summary>
        public long LastOperateDate { get; set; }
        private long FileLenV;
        /// <summary>
        /// 获取文件的长度
        /// </summary>
        public long FileLength
        {
            get
            {
                if (!IsWrite)
                    return BaseStream.Length;
                else
                    return FileLenV;
            }
            private set { FileLenV = value; }
        }

        /// <summary>
        /// 表示该文件的基础流
        /// </summary>
        public FileStream BaseStream { get; private set; }
        /// <summary>
        /// 通过基础流构建文件项的新实例
        /// </summary>
        /// <param name="BaseStream">该文件的基础文件流</param>
        public FileItemInfo(FileStream BaseStream, bool IsWrite = false, long FileLength = 0)
        {
            this.BaseStream = BaseStream;
            this.LastOperateDate = DateTime.Now.ToBinary();
            this.RefCount = 1;
            this.IsWrite = IsWrite;
            this.FileLength = FileLength;
        }
    }

    /// <summary>
    /// 表示单个文件请求
    /// </summary>
    public sealed class FileRequestItem
    {
        /// <summary>
        /// 表示该文件请求最后一次请求的内容
        /// </summary>
        public FileRequestObject LastRequest { get; internal set; }
        /// <summary>
        /// 表示该文件请求最后一次回复的内容
        /// </summary>
        public FileRequestObject LastResponce { get; internal set; }
        /// <summary>
        /// 表示该文件请求最后一次回复的时间
        /// </summary>
        public int LastResponceTime { get; internal set; }
        /// <summary>
        /// 表示该文件请求最后一次请求的时间
        /// </summary>
        public int LastRequestTime { get; internal set; }
        /// <summary>
        /// 表示该文件请求已重试的次数
        /// </summary>
        public int RetryCount { get; internal set; }

        internal FileRequestItem()
        {
            LastRequestTime = Environment.TickCount;
            LastResponceTime = Environment.TickCount;
            RetryCount = 0;
        }
    }

    /// <summary>
    /// 为文件传输提供必要存储与方法
    /// </summary>
    [SettingParentDefine]
    public static class FilePool
    {

        #region========私有变量========

       /// <summary>
        /// 表示文件池中的文件列表
        /// </summary>
        private static ConcurrentDictionary<string, FileItemInfo> FileList;
        /// <summary>
        /// 表示问件池中的请求列表
        /// </summary>
        private static ConcurrentDictionary<int, FileRequestItem> RequestList;
        /// <summary>
        /// 文件请求自增ID
        /// </summary>
        private static int NowRequestID = 0;
        #endregion

        #region========公有委托========

        public delegate void BeforeOperateRequestEventHandler(FileRequestEventArgs e);
        public delegate void AfterOperateRequestEventHandler(FileRequestEventArgs e);
        public delegate void NeedToReRequestEventHandler(FileRequestEventArgs e);

        #endregion

        #region==========事件==========

        public static event BeforeOperateRequestEventHandler BeforeOperateRequest;
        public static event AfterOperateRequestEventHandler AfterOperateRequest;
        public static event NeedToReRequestEventHandler NeedToRequestEvent;

        #endregion

        #region==========属性==========

        /// <summary>
        /// 获取或设置每个文件缓冲区的大小
        /// </summary>
        [SettingItemDefine(SettingValueType.Int32,102400)]
        [Browsable(true)]
        [Category("常规")]
        [DisplayName("缓冲区大小")]
        [Description("获取或设置每个文件缓冲区的大小")]
        public static int BufferSize { get; set; }
        /// <summary>
        /// 获取或设置文件请求重发的时间间隔
        /// </summary>
        [SettingItemDefine(SettingValueType.Int32,5000)]
        [Browsable(true)]
        [Category("常规")]
        [DisplayName("重发等待时间")]
        [Description("获取或设置文件请求重发的时间间隔")]
        [DefaultValue(5000)]
        public static int RequestReSendTick { get; set; }
        /// <summary>
        /// 获取或设置单个文件请求重发的最大次数
        /// </summary>
        [SettingItemDefine(SettingValueType.Int32,5)]
        [Browsable(true)]
        [Category("常规")]
        [DisplayName("重发最大次数")]
        [Description("获取或设置单个文件请求重发的最大次数")]
        [DefaultValue(5)]
        public static int RequestReSendCount { get; set; }
        /// <summary>
        /// 获取或设置接收文件的保存文件夹
        /// </summary>
        [SettingItemDefine(SettingValueType.String,@"C:\FileRecive")]
        [Browsable(true)]
        [Category("常规")]
        [DisplayName("文件保存目录")]
        [Description("获取或设置接收文件的保存文件夹")]
        [DefaultValue(@"C:\FileRecive")]
        [Editor(typeof(FolderNameEditor), typeof(UITypeEditor))]
        public static string ReciveFileSaveDir { get; set; }

        #endregion

        #region========公有函数========

        /// <summary>
        /// 初始化文件池
        /// </summary>
        /// <returns></returns>
        public static bool Init()
        {
            FileList = new ConcurrentDictionary<string, FileItemInfo>();
            RequestList = new ConcurrentDictionary<int, FileRequestItem>();
            Task CheckTask = new Task(CheckAllRequest);
            CheckTask.Start();
            return true;
        }

        private static object DoReqLock = new object();
        /// <summary>
        /// 执行文件请求
        /// </summary>
        /// <param name="Obj">要执行的文件请求</param>
        /// <returns>要返回的文件请求</returns>
        public static bool DoRequestObject(FileRequestObject Obj, out FileRequestObject ReObj)
        {
            lock (DoReqLock)
            {
                ReObj = new FileRequestObject(Obj.FilePath, Obj.Tag);
                FileRequestItem FRequest;
                if (Obj.RequestID == -1)
                {

                    FRequest = new FileRequestItem();
                    if (RequestList.TryAdd(NowRequestID, FRequest))
                    {
                        Obj.RequestID = NowRequestID;
                        NowRequestID++;
                    }
                    else
                    {
                        throw new Exception("尝试创建请求项失败！");
                    }
                }
                else
                {
                    if (!RequestList.TryGetValue(Obj.RequestID, out FRequest))
                    {
                        throw new Exception("尝试获取请求项失败！");
                    }
                }
                FRequest.RetryCount = 0;
                FRequest.LastRequestTime = Environment.TickCount;
                FRequest.LastRequest = Obj;
                ReObj.RequestID = Obj.ResponceID;
                ReObj.ResponceID = Obj.RequestID;
                FileRequestEventArgs e = new FileRequestEventArgs(Obj, ReObj);
                try
                {
                    if (BeforeOperateRequest != null)
                    {
                        BeforeOperateRequest(e);
                    }
                    switch (Obj.ReauestType)
                    {
                        case FileRequestType.Open:
                            DoOpen(Obj, ref ReObj);
                            break;
                        case FileRequestType.OpenOrCreate:
                            DoOpenOrCreate(Obj, ref ReObj);
                            break;
                        case FileRequestType.Read:
                            DoRead(Obj, ref ReObj);
                            break;
                        case FileRequestType.Write:
                            DoWrite(Obj, ref ReObj);
                            break;
                        case FileRequestType.Close:
                            DoClose(Obj, ref ReObj);
                            break;
                        case FileRequestType.Stop:
                            DoStop(Obj, ref ReObj);
                            break;
                        case FileRequestType.Error:
                            ReObj = null;
                            break;
                    }
                    if (AfterOperateRequest != null)
                    {
                        AfterOperateRequest(e);
                    }
                    FRequest.LastResponceTime = Environment.TickCount;
                    FRequest.LastResponce = ReObj;
                    return true;
                }
                catch (Exception ex)
                {
#if DEBUG
                    throw;
#else
                    ReObj.ReauestType = FileRequestType.Error;
                    ReObj.Buffer = Encoding.UTF8.GetBytes(ex.Message);
                    if (AfterOperateRequest != null)
                    {
                        AfterOperateRequest(e);
                    }
                    return false;
#endif
                }
            }
        }

        private static object lock1 = new object();
        /// <summary>
        /// 为开始发送一个文件包装对象
        /// </summary>
        /// <param name="FileName">要发送的文件的路径</param>
        /// <param name="Tag">表示该文件发送项包含的自定义数据</param>
        /// <returns>包装过后的对象</returns>
        public static FileRequestObject BeginSendFile(string FilePath, object Tag)
        {
            lock (lock1)
            {
                FileRequestObject r = new FileRequestObject(FilePath, Tag);
                r.ReauestType = FileRequestType.Open;
                r.FileOffset = 0;
                return r;
            }
        }

        private static object lock2 = new object();
        /// <summary>
        /// 为停止发送一个文件包装对象
        /// </summary>
        /// <param name="FilePath">要停止发送的文件路径</param>
        /// <param name="Tag">表示该文件发送项包含的自定义数据</param>
        /// <returns>包装好的对象</returns>
        public static FileRequestObject StopSendFile(string FilePath, object Tag)
        {
            lock (lock2)
            {
                FileRequestObject r = new FileRequestObject(FilePath, Tag);
                r.ReauestType = FileRequestType.Stop;
                return r;
            }
        }

        private static object lock3 = new object();
        /// <summary>
        /// 为继续发送一个文件包装对象
        /// </summary>
        /// <param name="FilePath">要继续发送的文件路径</param>
        /// <param name="Offset">要继续发送的起始偏移</param>
        /// <param name="Tag">表示该文件发送项包含的自定义数据</param>
        /// <returns>包装好的对象</returns>
        public static FileRequestObject ContinueSendFile(string FilePath, long Offset, object Tag)
        {
            lock (lock3)
            {
                FileRequestObject r = new FileRequestObject(FilePath, Tag);
                r.ReauestType = FileRequestType.Open;
                r.FileOffset = Offset;
                return r;
            }
        }

        #endregion

        #region========私有函数========

        private static object lock4 = new object();
        /// <summary>
        /// 检查所有请求项
        /// </summary>
        private static void CheckAllRequest()
        {
            lock (lock4)
            {
                while (true)
                {
                    foreach (int key in RequestList.Keys.ToArray())
                    {
                        FileRequestItem item = RequestList[key];
                        if (Environment.TickCount - item.LastResponceTime > RequestReSendTick)
                        {
                            if (item.RetryCount >= RequestReSendCount)
                            {
                                FileRequestItem i;
                                RequestList.TryRemove(key, out i);
                                item.LastResponce.ReauestType = FileRequestType.Error;
                                item.LastResponce.Buffer = Encoding.UTF8.GetBytes("重试已超过最大重试次数，文件传输失败！");
                                FileRequestEventArgs e = new FileRequestEventArgs(item.LastRequest, item.LastResponce);
                                if (AfterOperateRequest != null)
                                {
                                    AfterOperateRequest(e);
                                }
                                continue;
                            }
                            FileRequestObject reobj;
                            DoRequestObject(item.LastRequest, out reobj);
                            if (NeedToRequestEvent != null)
                            {
                                FileRequestEventArgs e = new FileRequestEventArgs(item.LastRequest, reobj);
                                NeedToRequestEvent(e);
                            }
                            item.RetryCount++;
                        }
                    }
                    System.Threading.Thread.Sleep(RequestReSendTick);
                }
            }
        }

        private static object lock5 = new object();
        /// <summary>
        /// 执行打开操作
        /// </summary>
        /// <param name="Obj"></param>
        /// <param name="ReObj"></param>
        private static void DoOpen(FileRequestObject Obj, ref FileRequestObject ReObj)
        {
            lock (lock5)
            {
                FileItemInfo FStream;
                if (!FileList.TryGetValue(Obj.FilePath, out FStream))
                {
                    FStream = new FileItemInfo(new FileStream(Obj.FilePath, FileMode.Open, FileAccess.Read));
                    FileList.TryAdd(Obj.FilePath, FStream);
                }
                FileRequestItem FRequest = new FileRequestItem();
                ReObj.ReauestType = FileRequestType.OpenOrCreate;
                ReObj.Buffer = BitConverter.GetBytes(FStream.FileLength);
                ReObj.FileOffset = Obj.FileOffset;
            }
        }

        private static object lock6 = new object();
        /// <summary>
        /// 执行打开或创建工作
        /// </summary>
        /// <param name="Obj"></param>
        /// <param name="ReObj"></param>
        private static void DoOpenOrCreate(FileRequestObject Obj, ref FileRequestObject ReObj)
        {
            lock (lock6)
            {
                FileItemInfo FInfo;
                if (!FileList.TryGetValue(Obj.FilePath, out FInfo))
                {
                    if (!System.IO.Directory.Exists(ReciveFileSaveDir))
                    {
                        System.IO.Directory.CreateDirectory(ReciveFileSaveDir);
                    }
                    FileStream FStream = new FileStream(ReciveFileSaveDir + @"\" + Path.GetFileName(Obj.FilePath), FileMode.OpenOrCreate, FileAccess.ReadWrite);
                    FInfo = new FileItemInfo(FStream, true, BitConverter.ToInt64(Obj.Buffer, 0));
                    FileList.TryAdd(Obj.FilePath, FInfo);
                }
                ReObj.ReauestType = FileRequestType.Read;
                ReObj.FileOffset = Obj.FileOffset;
            }
        }

        private static object lock7 = new object();
        /// <summary>
        /// 执行读取操作
        /// </summary>
        /// <param name="Obj"></param>
        /// <param name="ReObj"></param>
        private static void DoRead(FileRequestObject Obj, ref FileRequestObject ReObj)
        {
            lock (lock7)
            {
                FileItemInfo FStream;
                if (!FileList.TryGetValue(Obj.FilePath, out FStream))
                {
                    throw (new Exception("操作违规：文件尚未打开，无法执行读取操作！"));
                }
                if (FStream.IsWrite)
                {
                    throw (new Exception("操作违规：文件正在被写入，无法执行读取操作！"));
                }
                int BufferS = (int)((Obj.FileOffset + BufferSize <= FStream.BaseStream.Length) ?
                    BufferSize :
                    FStream.BaseStream.Length - Obj.FileOffset);
                byte[] Buffer = new byte[BufferS];
                FStream.BaseStream.Seek(Obj.FileOffset, SeekOrigin.Begin);
                FStream.BaseStream.Read(Buffer, 0, BufferS);
                ReObj.Buffer = Buffer;
                ReObj.FileOffset = Obj.FileOffset;
                ReObj.ReauestType = FileRequestType.Write;
            }
        }

        private static object lock8 = new object();
        /// <summary>
        /// 执行写入操作
        /// </summary>
        /// <param name="Obj"></param>
        /// <param name="ReObj"></param>
        private static void DoWrite(FileRequestObject Obj, ref FileRequestObject ReObj)
        {
            lock (lock8)
            {
                FileItemInfo FStream;
                if (!FileList.TryGetValue(Obj.FilePath, out FStream))
                {
                    throw (new Exception("操作违规：文件尚未打开，无法执行写入操作！"));
                }
                if (!FStream.IsWrite)
                {
                    throw (new Exception("操作违规：文件正在被读取，无法执行写入操作！"));
                }
                FStream.BaseStream.Seek(Obj.FileOffset, SeekOrigin.Begin);
                FStream.BaseStream.Write(Obj.Buffer, 0, Obj.Buffer.Length);
                FStream.BaseStream.Flush();
                ReObj.FileOffset = FStream.BaseStream.Position;
                if (FStream.BaseStream.Length > FStream.FileLength)
                {
                    throw (new Exception("操作违规：文件已接收大小大于文件理论大小，文件已损坏！"));
                }
                else if (FStream.BaseStream.Length < FStream.FileLength)
                {

                    ReObj.ReauestType = FileRequestType.Read;
                }
                else
                {
                    ReObj.ReauestType = FileRequestType.Close;
                    if (FStream.RefCount > 1)
                    {
                        FStream.RefCount--;
                    }
                    else
                    {
                        if (FileList.TryRemove(Obj.FilePath, out FStream))
                        {
                            FileRequestItem FreItem;
                            RequestList.TryRemove(Obj.RequestID, out FreItem);
                            FStream.BaseStream.Close();
                        }
                    }
                }
            }
        }

        private static object lock9 = new object();
        /// <summary>
        /// 执行关闭操作
        /// </summary>
        /// <param name="Obj"></param>
        /// <param name="ReObj"></param>
        private static void DoClose(FileRequestObject Obj, ref FileRequestObject ReObj)
        {
            lock (lock9)
            {
                FileItemInfo FStream;
                if (!FileList.TryGetValue(Obj.FilePath, out FStream))
                {
                    throw (new Exception("操作违规：文件尚未打开，无法执行关闭操作！"));
                }
                long len = FStream.BaseStream.Length;
                if (FStream.RefCount > 1)
                {
                    FStream.RefCount--;
                }
                else
                {
                    if (FileList.TryRemove(Obj.FilePath, out FStream))
                    {
                        FStream.BaseStream.Close();
                    }
                }
                FileRequestItem FreItem;
                RequestList.TryRemove(Obj.RequestID, out FreItem);
                if (Obj.FileOffset != len)
                {
                    throw (new Exception("操作违规：文件大小不符！文件已损坏！"));
                }
                ReObj = null;
            }
        }

        private static object lock10 = new object();
        /// <summary>
        /// 执行终止操作
        /// </summary>
        /// <param name="Obj"></param>
        /// <param name="ReObj"></param>
        private static void DoStop(FileRequestObject Obj, ref FileRequestObject ReObj)
        {
            lock (lock10)
            {
                FileItemInfo FStream;
                if (!FileList.TryGetValue(Obj.FilePath, out FStream))
                {
                    throw (new Exception("操作违规：文件尚未打开，无法执行关闭操作！"));
                }
                if (FStream.RefCount > 1)
                {
                    FStream.RefCount--;
                }
                else
                {
                    if (FileList.TryRemove(Obj.FilePath, out FStream))
                    {
                        FStream.BaseStream.Close();
                    }
                }
                ReObj = null;
            }
        }

        #endregion

    }
}
